﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.Runtime.InteropServices;
using System.Drawing;


namespace acr_server
{
    class ImageReceiver
    {
        class ProtocolErrorException : Exception
        {
            public ProtocolErrorException() : base() { }
            public ProtocolErrorException(string str) : base(str) { }
        }
        class TimeOutException : Exception
        {
            public TimeOutException() : base() { }
            public TimeOutException(string str) : base(str) { }
        }
        public enum Command
        {
            IMG = 1,
            CONTOUR = 2,
            ACK = 0xA,
            NAK = 0xB
        }
        const int MAGIC = 0x00ABCDEF;
        const int CONNECTION_TIMEOUT = 3000;
        const int PACKET_TIMEOUT = 15000;

        Socket listener;
        List<Socket> clients;
        volatile bool isServerRunning;
        List<Thread> threads;

        TextWriter logStream;
        Image currentImage;
        public bool Success  =false;
        public Image CurrentImage
        {
            get { return currentImage; }
        }

        public ImageReceiver(TextWriter logStream) 
        {
            threads = new List<Thread>();
            clients = new List<Socket>();
            this.logStream = logStream;
            StartListening(12345);
        }
        private void StartListening(int port)
        {
            try
            {
                isServerRunning = true;
                listener = new Socket(
                    AddressFamily.InterNetwork,
                    SocketType.Stream,
                    ProtocolType.Tcp);

                IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, port);
                listener.Bind(endPoint);
                listener.Listen(12);
                logStream.WriteLine("Socket created. Listening port " + port.ToString());
                SocketAccepter();
                Success = true;
            }
            catch (Exception e)
            {
                System.Windows.Forms.MessageBox.Show("Unexpected error while opennig port: " + e.Message);
                System.Windows.Forms.Application.Exit();
            }

        }
        private void SocketAccepter()
        {
            Thread thread = new Thread(delegate()
            {
#if DEBUG
                try
                {
                    //currentImage = Image.FromFile("D:\\test.jpg");
                    //byte[] b = File.ReadAllBytes("D:\\test.jpg");
                    //Point[] p = acr_algo_dotnet_wrapper.Acr_Wrapper.GetHandCountour(b);
                    //DrawCountour(currentImage, p);
                }
                catch (Exception)
                {
                    //logStream.WriteLine("ERROR occured in debug code: " + e.Message);
                }

#endif
                while (isServerRunning)
                {
                    try
                    {

                        logStream.WriteLine("Waiting for connection...");
                        Socket client = listener.Accept();
                        clients.Add(client);
                        logStream.WriteLine("Client connected: " + client.RemoteEndPoint.ToString());
                        Thread recvThread = new Thread(delegate()
                        {
                            Receiver(client);
                        });
                        recvThread.Start();
                        threads.Add(recvThread);
                    }
                    
                    catch (Exception e)
                    {
                        logStream.WriteLine("Exception occured: " + e.Message + "\n Debug Info:" + e.Source + "\nStack Trace:" + e.StackTrace);
                    }
                }
                foreach (Socket client in clients)
                {
                    if (client != null)
                        if (client.Connected)
                            logStream.WriteLine(client.RemoteEndPoint.ToString() + " connected, closing");
                        client.Close();
                }
                
            });
            thread.Start();
            threads.Add(thread);
        }
        private void Receiver(Socket client)
        {
            try
            {
                logStream.WriteLine(client.RemoteEndPoint.ToString() + ": Receiving image...");
                byte[] img = ReceiveImage(client);
                logStream.WriteLine(client.RemoteEndPoint.ToString() + ": Image received");
                Image image = Image.FromStream(new MemoryStream(img));
#if DEBUG
                File.WriteAllBytes("debug_image" + Environment.TickCount.ToString() + ".jpg", img);
#endif
                currentImage = (Image)image.Clone();
                logStream.WriteLine(client.RemoteEndPoint.ToString() + ": Generating contour...");
                Point[] contour = acr_algo_dotnet_wrapper.Acr_Wrapper.GetHandCountour(img);
                logStream.WriteLine(client.RemoteEndPoint.ToString() + ": Contour generated. Sending...");
                DrawCountour(image, contour);
                SendContour(client, contour, image.Width, image.Height);
                logStream.WriteLine(client.RemoteEndPoint.ToString() + ": Contour sent");
            }
            catch (TimeOutException)
            {
                logStream.WriteLine(client.RemoteEndPoint.ToString() + ": ERROR: timeout ");
            }
            catch (ProtocolErrorException e)
            {
                //client.Send(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)Command.NAK)));
                logStream.WriteLine(client.RemoteEndPoint.ToString() + ": Protocol error: " + e.Message);
            }
            catch (SocketException e)
            {
                logStream.WriteLine(client.RemoteEndPoint.ToString() + ": Connection error: " + e.Message);
            }
            catch (Exception e)
            {
                logStream.WriteLine(client.RemoteEndPoint.ToString() + ": Exception occured: " + e.Message + "\n Debug Info:" + e.Source + "\nStack Trace:" + e.StackTrace);
            }
                
            finally
            {
                client.Close();
            }
        }
        void DrawCountour(Image image, Point[] points)
        {
            if (points.Length == 0)
            {
                return;
            }
            Graphics gr = Graphics.FromImage(image);
            gr.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
            Pen pen = new Pen(Color.GreenYellow);
            pen.Width = 20;
            gr.DrawLines(pen, points);
            gr.DrawLine(pen, points[0], points[points.Length - 1]);
            currentImage = (Image)image.Clone();
        }
        public void CloseSockets()
        {
            isServerRunning = false;
            listener.Close();
            foreach (Thread thread in threads)
            {
                thread.Abort();
            }
        }
        public static byte[] ReceiveImage(Socket client)
        {
            byte[] bytes = new byte[8];
            Receive(client, bytes, bytes.Length, CONNECTION_TIMEOUT);
                
            int cmd = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(bytes, 0));

            if (cmd != (int)Command.IMG)
            {
                throw new ProtocolErrorException("Unexpected command");
            }
            int size = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(bytes, 4));
            byte[] img = new byte[size];
            Receive(client, img, img.Length, PACKET_TIMEOUT);
            byte[] magicData = new byte[4];
            Receive(client, magicData, magicData.Length, PACKET_TIMEOUT);
            int magic = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(magicData, 0));
            if (magic != MAGIC)
            {
                throw new ProtocolErrorException("Magic const checking failed");
            }
            client.Send(BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)Command.ACK)));
            return img;
        }
        public void SendContour(Socket client, Point[] contour, int x, int y)
        {
            byte[] cmd = new byte[8];

            BitConverter.GetBytes(IPAddress.HostToNetworkOrder((int)Command.CONTOUR)).CopyTo(cmd, 0);
            BitConverter.GetBytes(IPAddress.HostToNetworkOrder(contour.Length)).CopyTo(cmd, 4);
            byte[] contourData = ConvertPoints(contour, x, y);
            byte[] magic = BitConverter.GetBytes(IPAddress.HostToNetworkOrder(MAGIC));
            client.Send(cmd);
            client.Send(contourData);
            client.Send(magic);
        }
        public static byte[] ConvertPoints(Point[] list, int x, int y)
        {
            byte[] res = new byte[list.Length * 8];
            int pos = 0;
            foreach (Point element in list)
            {
                int X = (int)((element.X/(double)(x))*10000);
                int Y = (int)((element.Y/(double)(y))*10000);
                BitConverter.GetBytes(IPAddress.HostToNetworkOrder(X)).CopyTo(res, pos);
                pos += 4;
                BitConverter.GetBytes(IPAddress.HostToNetworkOrder(Y)).CopyTo(res, pos);
                pos += 4;
            }
            return res;
        }
        public static void Receive(Socket socket, byte[] buffer, int size, int timeout)
        {
            int startTickCount = Environment.TickCount;
            int received = 0;
            do
            {
                if (Environment.TickCount > startTickCount + timeout)
                    throw new TimeOutException("Timeout.");
                try
                {
                    if (socket.Available > 0)
                    {
                        received += socket.Receive(buffer, received, size - received, SocketFlags.None);
                        startTickCount = Environment.TickCount;
                    }
                }
                catch (SocketException ex)
                {
                    if (ex.SocketErrorCode == SocketError.WouldBlock ||
                        ex.SocketErrorCode == SocketError.IOPending ||
                        ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
                    {
                        // socket buffer is probably empty, wait and try again
                        Thread.Sleep(30);
                    }
                    else
                        throw ex;  // any serious error occurr
                }
            } while (received < size);
        }
    }
}
